#!/usr/bin/env python

import pygtk
pygtk.require('2.0')
import gtk, gobject
import threading

import os
import pynotify

# =============================================================================
class KeyCombo:

	KEYNAME_CONTROL = "Control_L"
	KEYNAME_SHIFT = "Shift_L"
	KEYNAME_ALT = "Alt_L"

	# ---------------------------------------------------------------------
	def __init__(self, key, modifiers=[]):
		self.key = key
		self.modifiers = modifiers

		self.held_keys = []

	# ---------------------------------------------------------------------
	def press(self):
		self.keypressWithModifiers(self.key, self.modifiers)

	# ---------------------------------------------------------------------
	def pressDown(self):
		for k in self.modifiers:
			self.keydown(k)
			self.held_keys.append(k)

		# XXX I only tried this route to get the ALT+Tab application switcher working properly (see notes below).
		self.keyinstantaneous(self.key)
#		self.keydown(self.key)
#		self.held_keys.append(self.key)

		# We reverse the list so they come up in LIFO order
		self.held_keys.reverse()

	# ---------------------------------------------------------------------
	def releaseHeldKeys(self):
		for k in self.held_keys:
			self.keyup(k)

		self.held_keys = []

	# ---------------------------------------------------------------------
	def keypressWithModifiers(self, key, modifiers=[]):
		"""Wraps a keypress with key modifiers, recursively"""

		if modifiers:
			modifier = modifiers[0]
			self.keydown(modifier)
			self.keypressWithModifiers(key, modifiers[1:])
			self.keyup(modifier)
		else:
			self.keyinstantaneous(key)

	# ---------------------------------------------------------------------
	def keyinstantaneous(self, key):
		self.xteCommand("key", key)

	# ---------------------------------------------------------------------
	def keydown(self, key):
		self.xteCommand("keydown", key)

	# ---------------------------------------------------------------------
	def keyup(self, key):
		self.xteCommand("keyup", key)

	# ---------------------------------------------------------------------
	def xteCommand(self, command, key):
		args_list = ["xte", "\"" + command + " " + key + "\""]
		command = " ".join(args_list)
		print "About to execute:", command
		os.system( command )

# =============================================================================
class ServerThread(threading.Thread):

	"""Monitors the character device for input.
	Schema:
	Upon each pedal action, 24 characters are written to the device.
	This consists of three groups of 8 characters apiece.
	Each group contains a prefix of the form [X, 0, 9, 0], where X
	is the pedal index, followed by [Y, 0, 0, 0], where Y is either
	1 or 0 (pressed or not pressed)."""

	HID_DEVICE_PATH_TEMPLATE = "/dev/usb/hiddev%d"
	PEDAL_COUNT = 3
	GROUP_SIZE = 8
	DEPRESSED_BYTE_INDEX = 4
	WORD_LENGTH = PEDAL_COUNT*GROUP_SIZE

	PEDAL_FILTER_PRIORITY = [0, 2, 1]	# Priority: Left, Right, Center

	stopthread = threading.Event()

	# ---------------------------------------------------------------------
	def __init__(self, app_window, hid_device_index):
		threading.Thread.__init__(self)

		self.resetting = False

		self.app_window = app_window	# Maintain a reference to the GUI
		self.raw_previous_pedal_state = [0]*self.PEDAL_COUNT	# Initialize pedal positions to zero

		# Check whether your notification agent support
		# icon-summary-body layout.
		self.pynotify_supported = pynotify.init("icon-summary-body")
		if self.pynotify_supported:
			self.reusable_notification = pynotify.Notification(
						"You have a mail",
						"You have new e-mail from Milinda",
						"notification-message-email")

			self.reusable_notification.attach_to_status_icon(self.app_window.my_status_icon)
			self.reusable_notification.set_urgency(pynotify.URGENCY_LOW)	# Doesn't do anything
			self.reusable_notification.set_timeout(1000)	# Doesn't do anything

		# This is a dictionary of lists.
		# When a pedal undergoes a "sustained press", the keycombo object
		# is stored under that pedal's index.
		self.held_key_combos = [[] for i in range(self.PEDAL_COUNT)]

	# ---------------------------------------------------------------------
	def error_dialog_dismiss_callback(self, dialog, response_id):
		print("Exiting...")
		exit(1)

	# ---------------------------------------------------------------------
	def open_device(self, device_index):

		hid_device_path = self.HID_DEVICE_PATH_TEMPLATE % (device_index)

		hidfile = None
		try:
			hidfile = open(hid_device_path)
		except IOError:

			msg = "You need permission to access the device. Try entering the following command:\n"
			msg += "sudo chmod a+r " + hid_device_path

			print(msg)

			gtk.gdk.threads_enter()
			dialog = gtk.MessageDialog(parent=self.app_window, flags=0,
				buttons=gtk.BUTTONS_CLOSE, type=gtk.MESSAGE_ERROR,
				message_format="Permission lacking")
			dialog.connect("response", self.error_dialog_dismiss_callback)
			dialog.format_secondary_text(msg)
			dialog.show()
			gtk.gdk.threads_leave()
			return None

		# Trick adopted from http://stackoverflow.com/questions/375427/non-blocking-read-on-a-stream-in-python/1810703#1810703
		# makes a non-blocking file
		import fcntl, os
		fd = hidfile.fileno()
		fl = fcntl.fcntl(fd, fcntl.F_GETFL)
		fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

		return hidfile

	# ---------------------------------------------------------------------
	def run(self):
		'''Note that the scope of the HID filehandle exists only within this function.'''

		# The device selection loop; allows the device to be changed
		while True:

			self.resetting = False

			hidfile = self.open_device(self.app_window.hid_device_index)
			if not hidfile:
				return

			count = 0
			# Keep reading the character device
			while True:

				# If there is not data of the specified length available,
				# then the read() function throws an exception.
				try:
					mystring = hidfile.read(self.WORD_LENGTH)
				except:
					if self.stopthread.isSet():
						break

					import time
					time.sleep(0.05)
					continue

				pedal_byte_values = map(ord, mystring)
#				print("Numeric representation of string:", pedal_byte_values)

				# Split message into signals for each pedal
				pedal_signals = [pedal_byte_values[self.GROUP_SIZE*i:self.GROUP_SIZE*(i+1)] for i in range(self.PEDAL_COUNT)]
				raw_current_pedal_state = [bool(signal[self.DEPRESSED_BYTE_INDEX]) for signal in pedal_signals]

				# Queue an action to be performed in the main thread.
				gobject.idle_add(self.handle_pedal_press, raw_current_pedal_state)

				count += 1

			print("Closing device...")
			hidfile.close()

			# We may just be choosing a different device.
			if self.resetting:
				self.stopthread.clear()
			else:
				break				

		print("Finishing thread...")

	# ---------------------------------------------------------------------
	def handle_pedal_press(self, raw_current_pedal_state):
		"""We interact with the UI in this function, so it must happen in the UI thread via idle_add()"""

#		print("Current state:", raw_current_pedal_state, "Previous state:", self.raw_previous_pedal_state)
		self.app_window.set_foot_position_icon( any(raw_current_pedal_state) )

		# Update treeview and read "disabled" status
		treestore = self.app_window.pedalpress_treeview.get_model()
		toplevel_iter = treestore.get_iter_first()
		pedals_enabled = []
		for i, value in enumerate(raw_current_pedal_state):
			treestore.set_value(toplevel_iter, FootpedalAppWindow.TREESTORE_COLUMN_PEDAL_DEPRESSED, value)
			pedals_enabled.append( treestore.get_value(toplevel_iter, FootpedalAppWindow.TREESTORE_COLUMN_PEDAL_ENABLED) )
			toplevel_iter = treestore.iter_next(toplevel_iter)

		if self.app_window.enable_sustained_keypresses.get_active():
			self.handle_sustained_pedal_press(raw_current_pedal_state, pedals_enabled)
		else:
			self.handle_instantaneous_pedal_press(raw_current_pedal_state, pedals_enabled)

		self.raw_previous_pedal_state = raw_current_pedal_state

	# ---------------------------------------------------------------------
	def handle_instantaneous_pedal_press(self, raw_current_pedal_state, pedals_enabled):

		# Only perform an action if all of the pedals were in the "up" position first.
		# That means that simultaneous pressing of different pedals is disallowed.
		if not any(self.raw_previous_pedal_state):

			filtered_pedal = self.getSingleActivePedal(raw_current_pedal_state, pedals_enabled)
			if filtered_pedal >= 0:
				# Perform pedal action:
				pedal_action = None

				if self.app_window.cycling_functions_checkbox.get_active():
					pedal_function_group_index = self.app_window.cycle_function_selector.get_active()
					pedal_action = self.app_window.pedal_cycling_groups[pedal_function_group_index][filtered_pedal]
					if pedal_action:
						keycombo = pedal_action.action
						keycombo.press()

					else:
						new_index = (pedal_function_group_index + 1) % len(self.app_window.pedal_cycling_groups)
						self.app_window.cycle_function_selector.set_active( new_index )
						pedal_action = self.app_window.cycle_function_selector.get_active_text()
				else:
					pedal_function_group_index = self.app_window.pedal_function_selector.get_active()
					pedal_action = self.app_window.pedal_function_groups[pedal_function_group_index][filtered_pedal]
					keycombo = pedal_action.action
					keycombo.press()

				print(pedal_action.name)

				if self.pynotify_supported:
					self.reusable_notification.update("Foot pedal function:", pedal_action.name)
					self.reusable_notification.show()

	# ---------------------------------------------------------------------
	# XXX This function turned out to be not very useful.
	# The Compiz application switcher requires the ALT key to be held down
	# while navigating through the applications with the left and right arrows.
	# This does not map straightforwardly to my two-pedal scheme.
	# XXX Right now, the application switcher is broken. If I really wanted to get this to work,
	# I'd have to code a special case to accomodate the app switcher, possibly one where
	# the alt key maintains being held down until the user taps the middle pedal.
	def handle_sustained_pedal_press(self, raw_current_pedal_state, pedals_enabled):
		"""We are more restrictive about pressing down a pedal than relasing the pedal.
		This is because the middle pedal often gets accidentally pressed after the side pedals,
		so we want to ignore that.  However, we don't want any pedals getting stuck.  This is why
		we process any "release" action that we detect."""

		newly_released_pedals = [self.raw_previous_pedal_state[i] and not raw_current_pedal_state[i] for i in range(len(raw_current_pedal_state))]
		newly_depressed_pedals = [raw_current_pedal_state[i] and not self.raw_previous_pedal_state[i] for i in range(len(raw_current_pedal_state))]


		# First, we process the released pedals.
		for i, released in enumerate(newly_released_pedals):
			if released:
				for combo in self.held_key_combos[i]:
					combo.releaseHeldKeys()
				self.held_key_combos[i] = []	# Clear the list after releasing all keys


		if not any(self.raw_previous_pedal_state):

			filtered_pedal = self.getSingleActivePedal(raw_current_pedal_state, pedals_enabled)
			if filtered_pedal >= 0:
				# Perform pedal action:

				if self.app_window.cycling_functions_checkbox.get_active():
					pedal_function_group_index = self.app_window.cycle_function_selector.get_active()
					keycombo = self.app_window.cycling_function_keys[pedal_function_group_index][filtered_pedal]
					if keycombo:
						self.held_key_combos[filtered_pedal].append(keycombo)
						keycombo.pressDown()
						action_name = self.app_window.pedal_cycling_groups[pedal_function_group_index][filtered_pedal]
					else:
						new_index = (pedal_function_group_index + 1) % len(self.app_window.pedal_cycling_groups)
						self.app_window.cycle_function_selector.set_active( new_index )
						action_name = self.app_window.cycle_function_selector.get_active_text()
				else:
					pedal_function_group_index = self.app_window.pedal_function_selector.get_active()
					keycombo = self.app_window.pedal_function_groups[pedal_function_group_index][filtered_pedal].action
					self.held_key_combos[filtered_pedal].append(keycombo)
					keycombo.pressDown()
					action_name = self.app_window.pedal_function_groups[pedal_function_group_index][filtered_pedal]

				print(action_name)

				if self.pynotify_supported:
					self.reusable_notification.update("Foot pedal function:", action_name)
					self.reusable_notification.show()

	# ---------------------------------------------------------------------
	def getSingleActivePedal(self, raw_current_pedal_state, pedals_enabled):
		filtered_pedal = -1
		for idx in self.PEDAL_FILTER_PRIORITY:
			if raw_current_pedal_state[idx] and pedals_enabled[idx]:
				filtered_pedal = idx
				break

		return filtered_pedal

# =============================================================================
class PedalCommand:
	def __init__(self, name, action):
		self.name = name
		self.action = action

# =============================================================================
class FootpedalAppWindow(gtk.Window):

	appname = "footpedal"
	version = "0.5"
	local_share_dir = "/usr/share/"

	icon_filename = appname + ".png"

	FOOT_IMAGES = ["foot_up.png", "foot_down.png"]
	PEDAL_LABELS = ["Left", "Center", "Right"]

	TREESTORE_COLUMN_FUNCTION_NAME = 1
	TREESTORE_COLUMN_PEDAL_DEPRESSED = 2
	TREESTORE_COLUMN_PEDAL_ENABLED = 3

	WINDOW_PADDING = 5

	# ---------------------------------------------------------------------
	import os
	AUTOSTART_BASEPATH = os.path.expanduser("~/.config/autostart")
	APPLICATIONS_MENU_BASEPATH = os.path.abspath("/usr/share/applications")
	DESKTOP_ICON_NAME = "footpedal.desktop"
	STARTUP_LINK_PATH = os.path.join(AUTOSTART_BASEPATH, DESKTOP_ICON_NAME)
	STARTUP_LINK_TARGET = os.path.join(APPLICATIONS_MENU_BASEPATH, DESKTOP_ICON_NAME)

	def toggle_startup_launcher(self, widget):
		if widget.get_active():
			if not os.path.islink(self.STARTUP_LINK_PATH):
				os.symlink(self.STARTUP_LINK_TARGET, self.STARTUP_LINK_PATH)
		else:
			if os.path.islink(self.STARTUP_LINK_PATH):
				os.remove(self.STARTUP_LINK_PATH)

	# ---------------------------------------------------------------------
	def __init__(self, run_installed=True, show_panel=False, hid_device_index=0):
		gtk.Window.__init__(self)

		self.hid_device_index = hid_device_index

		self.run_installed = run_installed
		if run_installed:
			self.img_directory = self.local_share_dir + self.appname + "/"

		else:
			import sys
			self.img_directory = sys.path[0]

		from os import path
		self.icon_path = path.join(self.img_directory, self.icon_filename)


		# These are "default" or "builtin" presets
		# They are grouped in threes (for 3 pedals)
		self.pedal_function_groups = [
			[
				PedalCommand("Cut", KeyCombo("x", [KeyCombo.KEYNAME_CONTROL])),
				PedalCommand("Copy", KeyCombo("c", [KeyCombo.KEYNAME_CONTROL])),
				PedalCommand("Paste", KeyCombo("v", [KeyCombo.KEYNAME_CONTROL])),
			], [
				PedalCommand("VolDown", KeyCombo("XF86AudioLowerVolume")),
				PedalCommand("Mute", KeyCombo("XF86AudioMute")),
				PedalCommand("VolUp", KeyCombo("XF86AudioRaiseVolume")),
			], [
				PedalCommand("Rew", KeyCombo("XF86AudioPrev")),
				PedalCommand("Play", KeyCombo("XF86AudioPlay")),
				PedalCommand("FF", KeyCombo("XF86AudioNext")),
			], [
				PedalCommand("Home", KeyCombo("Home")),
				PedalCommand("Down", KeyCombo("Down")),
				PedalCommand("PgDown", KeyCombo("Page_Down")),
			]
		]

		# Note how the middle pedal has no command; this signifies
		# that the pedal should be used to cycle among command groupings.
		self.pedal_cycling_groups = [
			[
				PedalCommand("Cycle Desktops left", KeyCombo("Left", [KeyCombo.KEYNAME_CONTROL, KeyCombo.KEYNAME_ALT])),
				None,
				PedalCommand("Cycle Desktops right", KeyCombo("Right", [KeyCombo.KEYNAME_CONTROL, KeyCombo.KEYNAME_ALT])),
			], [
				PedalCommand("Cycle Applications backward", KeyCombo("Tab", [KeyCombo.KEYNAME_ALT, KeyCombo.KEYNAME_SHIFT])),
				None,
				PedalCommand("Cycle Applications forward", KeyCombo("Tab", [KeyCombo.KEYNAME_ALT])),
			], [
				PedalCommand("Cycle Tabs backward", KeyCombo("Tab", [KeyCombo.KEYNAME_CONTROL, KeyCombo.KEYNAME_SHIFT])),
				None,
				PedalCommand("Cycle Tabs forward", KeyCombo("Tab", [KeyCombo.KEYNAME_CONTROL])),
			], [
				PedalCommand("Scroll web page up", KeyCombo("space", [KeyCombo.KEYNAME_SHIFT])),
				None,
				PedalCommand("Scroll web page down", KeyCombo("space")),
			]
		]


		self.connect("delete_event", self.delete_event)
		self.connect("destroy", self.destroy)
		self.set_border_width(2*self.WINDOW_PADDING)

		self.hidden_window = not show_panel


		self.populateUserInterface()

		# Set default controls values
		self.set_foot_position_icon(False)
		self.cycle_function_selector.set_active(0)
		self.pedal_function_selector.set_active(1)
		self.cb_toggle_cycling_functions(self.cycling_functions_checkbox)

		self.server_thread = None	# Will be populated externally

	# ---------------------------------------------------------------------
	def cb_select_device(self, spinbutton, data=None):
		self.hid_device_index = spinbutton.get_value_as_int()
		self.reset_pedal(spinbutton, data=None)

	# ---------------------------------------------------------------------
	def populateUserInterface(self):

		vbox = gtk.VBox(False, self.WINDOW_PADDING)


		button = gtk.CheckButton("Launch on startup")
		button.set_active(os.path.islink(self.STARTUP_LINK_PATH))
		button.connect("toggled", self.toggle_startup_launcher)
		vbox.pack_start(button, False, False)


       
		control_hbox = gtk.HBox(False, self.WINDOW_PADDING)
		vbox.pack_start(control_hbox, False, False)
		control_hbox.pack_start(gtk.Label("Device ID:"), False, False)

		myadj = gtk.Adjustment(self.hid_device_index, 0, 15, 1)
		device_selector_spinbutton = gtk.SpinButton(myadj, 0, 0)
		device_selector_spinbutton.set_numeric(True)
		device_selector_spinbutton.connect("value-changed", self.cb_select_device)
		control_hbox.pack_start(device_selector_spinbutton, False, False)


		self.cycling_functions_checkbox = gtk.CheckButton("Use cycling functions")
		self.cycling_functions_checkbox.connect("toggled", self.cb_toggle_cycling_functions)
		vbox.pack_start( self.cycling_functions_checkbox, False, False)


		lil_hbox = gtk.HBox(False, self.WINDOW_PADDING)
		lil_hbox.set_tooltip_text("Select pedal functions")
		vbox.pack_start( lil_hbox, False, False)
		lil_hbox.pack_start( gtk.Label("Operation Mode:"), True, True)



		self.pedal_function_selector = gtk.combo_box_new_text()
		for pedal_function_group in self.pedal_function_groups:
			self.pedal_function_selector.append_text( "/".join([pedal_function.name for pedal_function in pedal_function_group]) )
		self.pedal_function_selector.append_text( "New..." )

		lil_hbox.pack_start( self.pedal_function_selector, False, False)
		self.pedal_function_selector.connect("changed", self.cb_functions_changed)

		self.cycle_function_selector = gtk.combo_box_new_text()
		for pedal_cycling_group in self.pedal_cycling_groups:
			self.cycle_function_selector.append_text( " ".join(pedal_cycling_group[0].name.split(" ")[:-1]) )
		lil_hbox.pack_start( self.cycle_function_selector, False, False)
		self.cycle_function_selector.connect("changed", self.cb_cycles_changed)
#		self.cycle_function_selector.set_no_show_all(True)

		self.edit_keystrokes_button = gtk.Button("Edit...")
		lil_hbox.pack_start( self.edit_keystrokes_button, False, False)
		self.edit_keystrokes_button.connect("clicked", self.cb_edit_dialog)



		ts = gtk.ListStore(str, str, bool, bool)
		for s in self.PEDAL_LABELS:
			ts.append( [s, "", False, True] )
		self.pedalpress_treeview = gtk.TreeView( ts )
		self.pedalpress_treeview.set_tooltip_text("Pedal status")
		self.pedalpress_treeview.set_rules_hint(True)	# alternates row coloring automatically
		self.pedalpress_treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_VERTICAL)

		for i, new_col_label in enumerate(["Pedal", "Function", "Depressed", "Enabled"]):

			if i<2:
				cell = gtk.CellRendererText()
				tvcolumn = gtk.TreeViewColumn(new_col_label, cell, text=i)
			else:
				cell = gtk.CellRendererToggle()
				tvcolumn = gtk.TreeViewColumn(new_col_label, cell, active=i)

				if i==self.TREESTORE_COLUMN_PEDAL_ENABLED:
					cell.connect("toggled", self.cb_cell_toggle)

			tvcolumn.set_expand(True)
			self.pedalpress_treeview.append_column(tvcolumn)

		vbox.pack_start(self.pedalpress_treeview, False, False)


		self.enable_sustained_keypresses = gtk.CheckButton("Enable sustained keypresses")
#		self.enable_sustained_keypresses.set_active(True)
#		vbox.pack_start( self.enable_sustained_keypresses, False, False)	# XXX This turned out to be not very useful

		self.add(vbox)
		vbox.show_all()


		self.my_status_icon = gtk.StatusIcon()
		self.my_status_icon.set_tooltip("Footswitch")
		self.my_status_icon.connect("activate", self.toggle_window)
		self.my_status_icon.connect("popup-menu", self.systray_popup_callback)
		self.set_icon_from_file( self.icon_path )

		self.set_title("Foot Pedal Configuration")

	# ---------------------------------------------------------------------
	def cb_toggle_cycling_functions(self, checkbox):

		if checkbox.get_active():

			self.cycle_function_selector.show()
			self.pedal_function_selector.hide()

			self.updateTreeviewForCyclingFunctions()
			self.edit_keystrokes_button.set_sensitive(False)


		else:
			self.cycle_function_selector.hide()
			self.pedal_function_selector.show()

			self.cb_functions_changed(self.pedal_function_selector)
			self.edit_keystrokes_button.set_sensitive(True)

	# ---------------------------------------------------------------------
	def cb_cell_toggle(self, cellrenderertoggle, path):

		treestore = self.pedalpress_treeview.get_model()
		my_iter = treestore.get_iter(path)
		old = treestore.get_value(my_iter, self.TREESTORE_COLUMN_PEDAL_ENABLED)
		treestore.set_value(my_iter, self.TREESTORE_COLUMN_PEDAL_ENABLED, not old)

	# ---------------------------------------------------------------------
	def cb_dialog_close(self, dialog):

		# FIXME: This never gets called.

		print("Foo")

		return True

	# ---------------------------------------------------------------------
	def cb_edit_dialog(self, widget):

		d = gtk.Dialog("Edit keys",
                     self,
                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                     (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
                      gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
		)

		d.connect("close", self.cb_dialog_close)	# This apparently does nothing.



		pedal_function_group_index = self.pedal_function_selector.get_active()

		ts = gtk.ListStore(str, str, str)
		if pedal_function_group_index < len(self.pedal_function_groups):
			for i in range(3):
				ts.append([
					self.PEDAL_LABELS[i],
					self.pedal_function_groups[pedal_function_group_index][i].name,
					self.pedal_function_groups[pedal_function_group_index][i].action.key
				])
		else:
			for i in range(3):
				ts.append([
					self.PEDAL_LABELS[i],
					"Command "+str(i),
					""
				])

		tv = gtk.TreeView( ts )
		tv.set_tooltip_text("Pedal status")
		tv.set_rules_hint(True)	# alternates row coloring automatically
		tv.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_VERTICAL)

		for i, new_col_label in enumerate(["Pedal", "Function", "Keystroke"]):
			cell = gtk.CellRendererText()

			
			tvcolumn = gtk.TreeViewColumn(new_col_label, cell, text=i)
			if i > 0:
				cell.set_property('editable', True)


			tvcolumn.set_expand(True)
			tv.append_column(tvcolumn)

		d.vbox.pack_start(tv, False, False)
		d.vbox.show_all()


		d.run()

		# TODO:
		# Save the new values.

		d.destroy()

	# ---------------------------------------------------------------------
	def updateTreeviewForCyclingFunctions(self):

		treestore = self.pedalpress_treeview.get_model()
		toplevel_iter = treestore.get_iter_first()

		for value in ["Backward", "Cycle functions", "Forward"]:
			treestore.set_value(toplevel_iter, self.TREESTORE_COLUMN_FUNCTION_NAME, value)
			toplevel_iter = treestore.iter_next(toplevel_iter)

	# ---------------------------------------------------------------------
	def cb_cycles_changed(self, widget):
		cycle_function_group_index = widget.get_active()
		# Does nothing

	# ---------------------------------------------------------------------
	def cb_functions_changed(self, widget):
		
		pedal_function_group_index = widget.get_active()
		if pedal_function_group_index == len(widget.get_model()) - 1:
			self.cb_edit_dialog(widget)
			return

		treestore = self.pedalpress_treeview.get_model()
		toplevel_iter = treestore.get_iter_first()

		function_group = self.pedal_function_groups[pedal_function_group_index]

		for pedal_function in function_group:
			treestore.set_value(toplevel_iter, self.TREESTORE_COLUMN_FUNCTION_NAME, pedal_function.name)
			toplevel_iter = treestore.iter_next(toplevel_iter)

	# ---------------------------------------------------------------------
	def show_preferences_dialog(self, widget):

		self.hidden_window = True
		self.toggle_window(widget)

	# ---------------------------------------------------------------------
	def cb_menuitem_activate(self, menuitem, idx):

		self.pedal_function_selector.set_active(idx)

		return False

	# ---------------------------------------------------------------------
	def build_menu(self):
		menu = gtk.Menu()

		command_submenu = gtk.Menu()

#		grp = None
		for i, pedal_function_group in enumerate(self.pedal_function_groups):

#			grp = gtk.RadioMenuItem( grp, "/".join(src) )
			grp = gtk.MenuItem( "/".join([pedal_function.name for pedal_function in pedal_function_group]) )
			grp.connect("activate", self.cb_menuitem_activate, i)
			command_submenu.append( grp )

#		grp.set_active(True)

		temp_item = gtk.MenuItem("Command set")
		temp_item.set_submenu( command_submenu )
		menu.append(temp_item)

		temp_item = gtk.MenuItem("Configure...")
		temp_item.connect("activate", self.show_preferences_dialog)
		menu.append(temp_item)

#		temp_item = gtk.MenuItem("Reset pedal")
#		temp_item.connect("activate", self.reset_pedal)
#		menu.append(temp_item)

		temp_item = gtk.ImageMenuItem(gtk.STOCK_QUIT)
		temp_item.connect("activate", self.destroy)
		menu.append(temp_item)

		menu.show_all()
		return menu

	# ---------------------------------------------------------------------
	def systray_popup_callback(self, status_icon, button, activate_time):
		my_popup_menu = self.build_menu()
		my_popup_menu.popup(None, None, None, button, activate_time, data=None)

	# ---------------------------------------------------------------------
	def toggle_window(self, status_icon):
		if self.hidden_window:
			self.show()
			self.hidden_window = False
		else:
			self.hide()
			self.hidden_window = True

	# ---------------------------------------------------------------------
	def set_foot_position_icon(self, down):

		from os import path
		icon_path = path.join(self.img_directory, self.FOOT_IMAGES[down])
		self.my_status_icon.set_from_file( icon_path )

	# ---------------------------------------------------------------------
	def delete_event(self, widget, event, data=None):

		self.hidden_window = False
		self.toggle_window(widget)

		return True
#		return False

	# ---------------------------------------------------------------------
	def reset_pedal(self, widget, data=None):

		self.server_thread.resetting = True

		print("Trying to reset pedal...")
		self.server_thread.stopthread.set()

	# ---------------------------------------------------------------------
	def destroy(self, widget, data=None):

		self.server_thread.stopthread.set()
		print("destroy signal occurred")
		gtk.main_quit()

	# ---------------------------------------------------------------------
	def startServerThread(self, widget, data=None):

		self.server_thread = ServerThread(self, self.hid_device_index)
		self.server_thread.start()

# =============================================================================
if __name__ == "__main__":

	from optparse import OptionParser
	usage = "usage: %prog [options]"
	parser = OptionParser(usage)
	parser.add_option("-d", action="store_false", default=True, dest="installed", help="Run for development")
	parser.add_option("-s", action="store_true", default=False, dest="showpanel", help="Start with panel shown")	# FIXME
	parser.add_option("-n", default=0, type=int, dest="device_index", help="HID device index")
	(options, args) = parser.parse_args()

	gobject.threads_init()

	fs = FootpedalAppWindow(options.installed, options.showpanel, options.device_index)
	fs.startServerThread(None)

	gtk.main()

	print("Shutting down...")

